package com.winit.schedule.core.task;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import com.winit.schedule.core.task.annotation.ScheduleTask;
import com.winit.schedule.core.utils.ArrayUtils;

/**
 * 调度任务定义扫描类(实现了任务处理接口并添加在任务注解信息）
 * 
 * @author jianke.zhang 2015-3-12 下午4:44:09
 * @since 1.0
 */
public class ScheduleTaskScanner {

    protected final Log             logger                  = LogFactory.getLog(getClass());

    private static final String     RESOURCE_PATTERN        = "/**/*.class";

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private List<String>            packagesList            = new LinkedList<String>();

    private List<TypeFilter>        typeFilters             = new LinkedList<TypeFilter>();

    private Set<ScheduleTaskDefine> scheduleTaskDefineSet   = new HashSet<ScheduleTaskDefine>();

    private String                  taskGroup;

    private List<String>            excludes;

    /**
     * 构造函数
     * 
     * @param packagesToScan 指定哪些包需要被扫描,支持多个包"package.a,package.b"并对每个包都会递归搜索
     * @param annotationFilter 指定扫描包中含有特定注解标记的bean,支持多个注解
     */
    public ScheduleTaskScanner(String taskGroup, String[] packagesToScan, List<String> excludes){
        if (StringUtils.isEmpty(taskGroup)) {
            throw new IllegalArgumentException("taskGroup cannot be null.");
        }
        this.taskGroup = taskGroup;
        this.excludes = excludes;

        if (packagesToScan != null) {
            for (String packagePath : packagesToScan) {
                logger.info("Initial task scan package:" + packagePath);

                this.packagesList.add(packagePath);
            }
        }
        typeFilters.add(new AnnotationTypeFilter(ScheduleTask.class, false));
    }

    /**
     * 将符合条件的Bean以Class集合的形式返回
     * 
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Set<ScheduleTaskDefine> scan() throws IOException, ClassNotFoundException {
        this.scheduleTaskDefineSet.clear();
        if (!this.packagesList.isEmpty()) {
            for (String pkg : this.packagesList) {
                String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                                 + ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
                Resource[] resources = this.resourcePatternResolver.getResources(pattern);
                MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        MetadataReader reader = readerFactory.getMetadataReader(resource);
                        if (matchesEntityTypeFilter(reader, readerFactory)) {
                            ScheduleTaskDefine scheduleTaskDefine = buildScheduleTaskDefine(reader);

                            // 判断多个任务处理类是否标记为相同的分组和名称
                            if (!this.scheduleTaskDefineSet.contains(scheduleTaskDefine)) {
                                this.scheduleTaskDefineSet.add(scheduleTaskDefine);
                            } else {
                                throw new IllegalArgumentException(String.format("The two TaskScheduleDeal cannot be labeled as the same task group and name, class: %s",
                                    scheduleTaskDefine));
                            }
                        }
                    }
                }
            }
        }
        // 输出日志
        if (logger.isInfoEnabled()) {
            for (ScheduleTaskDefine clazz : this.scheduleTaskDefineSet) {
                logger.info(String.format("Found task class:%s", clazz.getTaskName()));
            }
        }
        return this.scheduleTaskDefineSet;
    }

    /**
     * 检查当前扫描到的Bean含有任何一个指定的注解标记;以及是否实现调度任务接口：ScheduleTaskDeal
     * 
     * @param reader
     * @param readerFactory
     * @return
     * @throws IOException
     */
    private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory)
                                                                                                       throws IOException {
        if (!this.typeFilters.isEmpty()) {
            for (TypeFilter filter : this.typeFilters) {
                if (filter.match(reader, readerFactory)) {
                    String[] interfaceNames = reader.getClassMetadata().getInterfaceNames();
                    if (ArrayUtils.contain(interfaceNames, ScheduleTaskDealSingle.class.getName())) {
                        return true;
                    }
                    if (ArrayUtils.contain(interfaceNames, ScheduleTaskDealMulti.class.getName())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 构造调度任务定义信息
     * 
     * @param reader
     * @return
     */
    private ScheduleTaskDefine buildScheduleTaskDefine(MetadataReader reader) {
        String className = reader.getClassMetadata().getClassName();
        ScheduleTaskDefine scheduleTaskInfo = new ScheduleTaskDefine();
        scheduleTaskInfo.setTaskDealClass(className);

        reader.getAnnotationMetadata().getAnnotationAttributes("");
        Map<String, Object> attrs = reader.getAnnotationMetadata()
            .getAnnotationAttributes(ScheduleTask.class.getName());

        scheduleTaskInfo.setGroup(taskGroup);
        String name = attrs.get("taskName").toString();

        // 如未定义任务名，默认使用类名作为任务名称
        scheduleTaskInfo.setTaskName(StringUtils.isEmpty(name) ? className : name);
        scheduleTaskInfo.setExclude(excludes == null ? false : excludes.contains(name));

        String[] interfaceNames = reader.getClassMetadata().getInterfaceNames();
        if (ArrayUtils.contain(interfaceNames, ScheduleTaskDealSingle.class.getName())) {
            scheduleTaskInfo.setInterfaceType(TaskInterfaceType.SCHEDULE_TASK_SINGLE);
        }
        if (ArrayUtils.contain(interfaceNames, ScheduleTaskDealMulti.class.getName())) {
            scheduleTaskInfo.setInterfaceType(TaskInterfaceType.SCHEDULE_TASK_MUTIL);
        }
        return scheduleTaskInfo;
    }
}
